<?php
/*
 * @Author: your name
 * @Date: 2020-12-11 10:21:08
 * @LastEditTime: 2021-01-15 10:57:59
 * @LastEditors: Please set LastEditors
 * @Description: 客户端上传规则逻辑
 * @FilePath: /special/application/api/service/netfilter/FirewallRuleUploadService.php
 */
namespace app\api\service\netfilter;

use app\adminapiv3\model\learn_data\HostLearnDataModel;
use app\api\model\NetfilterFirewallRuleLogModel;
use app\common\definition\netfilter\FirewallRuleDefinition;
use app\common\exception\CustomException;
use think\Exception;
use think\Db;

class FirewallRuleUploadService extends FirewallRuleService
{

    //上传的规则内容
    protected $upload_content = [];
    //上传规则类型
    protected $upload_type = '';
    //上传规则类型
    protected $upload_version = '';
    //上传规则创建时间
    protected $upload_create_time = 0;
    //上传错误码
    protected $upload_error_code = 0;
    //上传错误信息
    protected $upload_error_message = '';
    //上传规则包含的防火墙配置
    protected $upload_firewall_set = [];
    //上传规则包含的防火墙规则
    protected $upload_firewall_set_rule = [];
    //预览规则
    protected $preview_content = [];
    //上传规则原型 属于直接入库 作为数据凭证 原则上不允许对数据改动
    protected $back_content = [];
    //预览规则id
    protected $preview_content_id = 0;
    function __construct($upload_host_key)
    {
        parent::__construct($upload_host_key);
    }

    /**
     * @name: 设置windows防火墙设置
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-18 13:25:06
     */
    public function setWindowsFirewallSetRule (array $states, array $default)
    {
        $init_rank = 1;
        foreach ($states as $rule)
        {
            preg_match('/netsh advfirewall set (private|public|domain)profile state (on|off)/i', strtolower($rule), $rule_a);
            if (empty($rule_a))
            {
                throw new CustomException('Windwos防火墙开关规则错误', 422);
            }
            $this->upload_firewall_set[$rule_a[1].'_main_switch'] = $rule_a[2] == 'on' ? 1 : 0;
            $this->upload_firewall_set_rule[] = [
                'rule_id' => $init_rank,
                'host_id' => $this->host_id,
                'content' => $rule,
                'rank' => $init_rank,
                'source' => FirewallRuleDefinition::FIREWALL_SET_RULE_SOURCE,
                'region' => FirewallRuleDefinition::FIREWALL_DEFAULT_CONFIG_RULE_REGION,
            ];
            $init_rank++;
        }
        foreach ($default as $rule)
        {
            preg_match('/netsh advfirewall set (private|public|domain)profile firewallpolicy (block|allow)inbound,(allow|block)outbound/i', strtolower($rule), $rule_a);
            
            if (empty($rule_a))
            {
                throw new CustomException('Windwos防火墙默认处理方式规则错误', 422);
            }

            $this->upload_firewall_set[$rule_a[1].'_input'] = $rule_a[2] == 'allow' ? 1 : 0;
            $this->upload_firewall_set[$rule_a[1].'_output'] = $rule_a[3] == 'allow' ? 1 : 0;
            $this->upload_firewall_set_rule[] = [
                'rule_id' => $init_rank,
                'host_id' => $this->host_id,
                'content' => $rule,
                'rank' => $init_rank,
                'source' => FirewallRuleDefinition::FIREWALL_SET_RULE_SOURCE,
                'region' => FirewallRuleDefinition::FIREWALL_DEFAULT_CONFIG_RULE_REGION,
            ];
            $init_rank++;
        }
    }

    /**
     * @name: 设置上传错误码
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-18 13:12:43
     */
    public function setUploadErrorCode (int $error_code)
    {
        $this->upload_error_code = $error_code;
    }

    /**
     * @name: 设置上传错误消息
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-18 13:12:43
     */
    public function setUploadErrorMessage (string $error_message)
    {
        $this->upload_error_message = $error_message;
    }

    /**
     * @name: 设置处理上传规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-18 11:13:23
     */
    public function setUploadRuleContent (array $content)
    {
        if(empty($content))
        {
            throw new CustomException('上传规则组错误', 422);
        }
        $content = array_map(function ($rule) {
            return trim(preg_replace('/\s{2,}/i', ' ', $rule));
        }, $content);
        //去除上传规则中可能存在的重复规则
        $content = array_unique($content);
        //对上传规则进行重新排序 避免 去重后出现 数组变对象问题
        $content = array_values($content);

        $this->back_content = $this->upload_content = $content;
    }

    /**
     * @name: 设置上传规则类型
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-18 11:21:56
     */
    public function setUploadRuleType (string $type)
    {
        $this->upload_type = $type;
    }

    /**
     * @name: 设置规则创建时间
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-18 11:25:24
     */
    public function setUploadRuleCreateTime(int $create_time)
    {
        if ($create_time <= 0)
        {
            return ;
        }
        
        $this->upload_create_time = $create_time;
    }
    /**
     * @name: 上传规则重置 当前规则源数据状态
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-03 11:16:21
     */
    protected function getDeleteFirewallRule (array $preview_rule, &$delete_id_a = [])
    {
        $preview_rule_content = array_column($preview_rule, 'content');

        $delete_rule = array_diff($preview_rule_content, $this->upload_content);
        
        $return = [];
        foreach ($delete_rule as $k => $v)
        {
            $return[$preview_rule[$k]['source']][] = $preview_rule[$k]['rule_id'];
            $delete_id_a[] = $preview_rule[$k]['id'];
        }

        return $return;

    }

    /**
     * @name: 
     * @Param: 
     * @Description: 添加原生规则并返回 需要添加的防火墙规则
     * @Author: foo
     * @Date: 2020-12-03 11:16:21
     */
    protected function getAddFirewallRule ($preview_rule)
    {
        $preview_rule_content = array_column($preview_rule, 'content');
        $insert_source_rule = array_diff($this->upload_content, $preview_rule_content);

        $return = [];
        foreach ($insert_source_rule as $rank => $content)
        {
            $rule_id = Db::name('host_initial_rule')->where('content', $content)->value('id');
                
            if(!$rule_id)
            {
                $rule_id = Db::name('host_initial_rule')->insertGetId([
                    'host_id' => $this->host_id,
                    'content' => $content,
                    'status' => FirewallRuleDefinition::RULE_ENABLE,
                ]);
            }
            
            $return[] = [
                'content' => $content,
                'rule_id' => $rule_id,
                'rank' => $rank,
                'source' => FirewallRuleDefinition::ORIGIN_RULE_SOURCE,
                'host_id' => $this->host_id,
                'region' => FirewallRuleDefinition::UNKNOW_RULE_REGION,
            ];
        }

        return $return;
    }

    /**
     * @name: 修改用户默认规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-11 11:14:17
     */
    protected function modifySourceRule ($source_id_a)
    {
        //原始规则 id组
        Db::name('host_initial_rule')->whereIn('id', $source_id_a)->update(
            [
                'status'=> FirewallRuleDefinition::RULE_DISABLE
            ]
        );
    }

    /**
     * @name: 修改通信规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-11 11:15:32
     */
    protected function modifySafeRule ($connect_id_a)
    {
        //通信规则 id组
        Db::name('netfilter_safe_rule')->whereIn('id', $connect_id_a)->update(
            [
                'status'=> FirewallRuleDefinition::RULE_DISABLE
            ]
        );
    }

    /**
     * @name: 归档防火墙日志
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-14 10:04:40
     */
    protected function archivedLog ()
    {
        $save_data = [
            'host_id' => $this->host_id,
            'create_time' => $this->upload_create_time,
            'signature_code' => md5($this->host_id . $this->upload_create_time),
            'preview_content' => json_encode($this->preview_content),
            'back_content' => json_encode($this->back_content),
            'error_code' => $this->upload_error_code,
            'error_message' => $this->upload_error_message,
            'status' => FirewallRuleDefinition::ARCHIVED_LOG_STATUS,
            'type' => $this->upload_type,
        ];

        $NetfilterFirewallRuleLogModel = new NetfilterFirewallRuleLogModel();
        if ($this->preview_content_id > 0)
        {
            $where = ['id' => $this->preview_content_id];
            $NetfilterFirewallRuleLogModel->where($where)->update($save_data);
        }else{
            $this->preview_content_id = $NetfilterFirewallRuleLogModel->insertGetId($save_data);
        }
    }

    /**
     * @name: 修改防火墙规则 添加新增的 删除未应用成功的
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-30 17:03:37
     */
    protected function resetFirewallRule ($delete_source_firewall_a, $delete_id_a, $add_firewall_a)
    {
        //子库数据库标识
        $learnDataModel = new HostLearnDataModel();
        $databaseKey = $learnDataModel->setHostId($this->host_id);
        //分布式事务
        try {
            Db::transactionXa(function () use ($learnDataModel, $delete_source_firewall_a, $delete_id_a, $add_firewall_a)
            {
                //修改自定义规则
                if(isset($delete_source_firewall_a[FirewallRuleDefinition::HANDLE_RULE_SOURCE]))
               {
                    $handle_id_a = $delete_source_firewall_a[FirewallRuleDefinition::HANDLE_RULE_SOURCE];
                    //查询对应的学习数据id
                    $learn_id_a = Db::name('host_rule')->whereIn('id', $handle_id_a)->where('type', FirewallRuleDefinition::HANDLE_RULE_MANUAL_TYPE)->column('learn_id');

                    $learnDataModel->whereIn('id', $learn_id_a)->where('host_id', $this->host_id)->update(
                        [
                            'status' => FirewallRuleDefinition::RULE_DISABLE
                        ]
                    );

                    //修改自定义规则表对应规则为 未启用
                   Db::name('host_rule')->whereIn('fid', $handle_id_a)->where('host_id', $this->host_id)->update(
                        [
                            'status'=> FirewallRuleDefinition::RULE_DISABLE
                        ]
                    );
                }
                //修改通信规则
                !isset($delete_source_firewall_a[FirewallRuleDefinition::CONNECT_RULE_SOURCE]) ? : $this->modifySafeRule($delete_source_firewall_a[FirewallRuleDefinition::CONNECT_RULE_SOURCE]);
                //修改原始规则
                !isset($delete_source_firewall_a[FirewallRuleDefinition::ORIGIN_RULE_SOURCE]) ? : $this->modifySourceRule($delete_source_firewall_a[FirewallRuleDefinition::ORIGIN_RULE_SOURCE]);
               //删除规则
               Db::name('netfilter_firewall_rule')->whereIn('id', $delete_id_a)->delete();
                //添加平台不存在的规则
                Db::name('netfilter_firewall_rule')->insertAll($add_firewall_a);

                $this->archivedLog();
            }, [Db::connect('mysql'),Db::connect($databaseKey)]);
        } catch (Exception $e) {
            throw new CustomException('保存规则信息失败:'. $e->getMessage(),201);
        }
    }
}